/* 
Parser for the C7 language
*/

%{
#include <iostream>
#include <string>
using namespace std;
void yyerror (const char *error);
extern "C" {
    int yyarse(void);
    int yylex(void);
    int yywrap() {
        return 1;
    }
}
enum VarType {
    TYPE_INT,
    TYPE_FLOAT
};
%}

%union {
    int value;    /* For the lexical analyzer. NUMBER tokens */
    char *ident;  /* For the lexical analyzer. IDENT tokens */
}

%token <ident> IDENT
%token <value> NUMBER
%token ASSIGNOP
%token GT
%token GTE
%token LT
%token LTE
%token EQ
%token NEQ
%token PLUSEQ
%token MINUSEQ
%token AND
%token OR
%token IF
%token ELSE
%token FI
%token WHILE
%token ELIHW
%token FOR
%token ROF
%token FUN
%token NUF
%token RETURN
%token INTTYPE
%token FLOATTYPE

%%

/* A program is a list of statements */
program: stmt_list

/* A list of statements including function declaration */
stmt_list: stmt stmt_list
| func_declare_stmt stmt_list
| stmt
| func_declare_stmt

/* A list of statements withou function declaration */
stmt_list_no_func_declare: stmt stmt_list_no_func_declare
| stmt

/* A statement can be an assignment, arithmetic, declarative,
 * or branching / looping statement 
 */
stmt: assign_stmt ';'
| arith_stmt ';' 
| declare_stmt ';'
| expr ';'
| if_stmt
| while_stmt
| for_stmt

/* Arithmetic assignment statements */
arith_stmt: IDENT PLUSEQ NUMBER
| IDENT PLUSEQ IDENT
| IDENT MINUSEQ NUMBER
| IDENT MINUSEQ IDENT
|

block: block_stmt block
| block_stmt

/* A block statement differs from a normal statement by 
 * disallowing declaration, and including a return statement.
 * This will be used only inside conditional if/for/while 
 * statements.
 */
block_stmt: assign_stmt ';'
| arith_stmt ';'
| RETURN expr ';'
| expr ';'
| if_stmt 
| while_stmt
| for_stmt

/*
 * Within the paranthesis inside 'for' only the below
 * statements are allowed.
 */
for_loop_stmt: assign_stmt
| arith_stmt
| expr

/* Variable declaration */
declare_stmt: INTTYPE declare_atom declare_args
| FLOATTYPE declare_atom declare_args

/* Declaring multiple variables. */
declare_args: ',' declare_atom declare_args
| /* empty */

/* Variable declaration including assignment.*/
declare_atom: IDENT
| assign_stmt

assign_stmt: IDENT ASSIGNOP expr 

if_stmt: IF '(' expr ')' ELSE FI
| IF '(' arith_stmt ')' ELSE FI
| IF '(' assign_stmt ')' ELSE FI
| IF '(' expr ')' block ELSE FI
| IF '(' arith_stmt ')' block ELSE FI
| IF '(' assign_stmt ')' block ELSE FI
| IF '(' expr ')' ELSE block FI
| IF '(' arith_stmt ')' ELSE block FI
| IF '(' assign_stmt ')' ELSE block FI
| IF '(' expr ')' block ELSE block FI
| IF '(' arith_stmt ')' block ELSE block FI
| IF '(' assign_stmt ')' block ELSE block FI
| IF '(' expr ')' FI
| IF '(' arith_stmt ')' FI
| IF '(' assign_stmt ')' FI
| IF '(' expr ')' block FI  
| IF '(' arith_stmt ')' block FI  
| IF '(' assign_stmt ')' block FI  

while_stmt: WHILE '(' expr ')' ELIHW
| WHILE '(' arith_stmt ')' ELIHW
| WHILE '(' assign_stmt ')' ELIHW
| WHILE '(' expr ')' block ELIHW
| WHILE '(' arith_stmt ')' block ELIHW
| WHILE '(' assign_stmt ')' block ELIHW

for_stmt: FOR '(' for_loop_stmt ';' for_loop_stmt ';' for_loop_stmt ')' ROF
| FOR '(' for_loop_stmt ';' for_loop_stmt ';' for_loop_stmt ')' block ROF

/* Declaration of a function. We check if there is atleast one 
 * return statement
 */
func_declare_stmt: FUN rettype IDENT '(' args_list ')' RETURN expr ';' NUF 
| FUN rettype IDENT '(' args_list ')' stmt_list_no_func_declare RETURN expr ';' NUF 
| FUN rettype IDENT '(' args_list ')' stmt_list_no_func_declare RETURN expr ';' stmt_list_no_func_declare NUF 

/* Return type for a function */
rettype: INTTYPE
| FLOATTYPE

/* Argument list within a function declaration.*/
args_list: args 
| /* empty */

args: INTTYPE IDENT
| FLOATTYPE IDENT
| INTTYPE IDENT ',' args
| FLOATTYPE IDENT ',' args

/* 
 * Split expression statements into expr, term, factor to 
 * avoid shift and reduce conflicts
 */
expr: expr '+' term1
| expr '-' term1   
| term1          

term1: term1 '%' term2 
| term1 '/' term2
| term1 '*' term2  
| term2

term2: term2 AND factor
| term2 OR factor
| factor          

factor: '(' expr ')' 
| '(' assign_stmt ')'
| '(' arith_stmt ')'
| NUMBER 
| IDENT 
| func_call
| IDENT EQ NUMBER
| IDENT EQ IDENT
| IDENT NEQ NUMBER
| IDENT NEQ IDENT
| IDENT GTE NUMBER
| IDENT GTE IDENT
| IDENT LTE NUMBER
| IDENT LTE IDENT
| IDENT GT NUMBER
| IDENT GT IDENT
| IDENT LT NUMBER
| IDENT LT IDENT

func_call: IDENT '(' call_args_list ')'

/* 
 * Argument list incase of function call. Does not allow
 * variable declaration but allows expressions. Either allow no
 * arguments, or a list of arguments such that none of it is empty.
 */
call_args_list: call_args
| /* empty */

call_args: expr 
| expr ',' call_args

%%

int main() {
    return yyparse();
}

void yyerror (const char *error) {
    cout << error << endl;
}
